﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using MongoDB.Driver;
using Xenta.Attributes;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Operations;
using Xenta.Utils;
#if CLOUD
using Microsoft.WindowsAzure.ServiceRuntime;
#endif

namespace Xenta.Plugins
{
    [Dependency("441e799f-d801-4a8c-81ea-b17fdb5a183b", 
        Lifestyle = DependencyLifestyle.Singleton, 
        Name = "Default File Storage")]
    public sealed class DefaultFileStorage : IFileStorage, IInitDependencyOnResolve
    {
        #region Constants

        private const int BUF_SIZE = 65536;

        #endregion

        #region Fields

        private readonly IInfrastructure _infrastructure;
        private string _storagePath;

        #endregion

        #region Ctors

        public DefaultFileStorage(IInfrastructure infrastructure)
        {
            _infrastructure = infrastructure;
            _storagePath = String.Empty;
        }

        #endregion

        #region Properties

        public IDictionary<String, String> State
        {
            get
            {
                var dirInfo = new DirectoryInfo(_storagePath);
                var driveInfo = new DriveInfo(dirInfo.Root.Name);
                var fileInfos = dirInfo.GetFiles();

                return new Dictionary<String, String>
                {
                    {"Path", dirInfo.FullName},
                    {"DirectoryName", dirInfo.Name},
                    {"DirectorySize", fileInfos.Sum(x => x.Length).ToString()},
                    {"FileCount", fileInfos.Length.ToString()},
                    {"DriveName", driveInfo.Name},
                    {"DriveLabel", driveInfo.VolumeLabel},
                    {"DriveFormat", driveInfo.DriveFormat},
                    {"DriveSize", driveInfo.TotalSize.ToString()},
                    {"DriveAvailableSpace", driveInfo.AvailableFreeSpace.ToString()},
                    {"DriveFreeSpace", driveInfo.TotalFreeSpace.ToString()}
                };
            }
        }

        #endregion

        #region Methods

        public void Initialize()
        {
            string storagePath = String.Empty;
            using(var ioc = _infrastructure.DependencyResolver())
            {
                var processor = ioc.Resolve<IOperationProcessor>().Init(new WorkingContext
                {
                    CID = new Guid("441e799f-d801-4a8c-81ea-b17fdb5a183b"),
                    IsSafe = true
                });
                try
                {
                    storagePath = processor.Execute(new GetConfigParamValueByKey
                    {
                        Key = Par.In("Plugin").Of(this).Name("Path")
                    }).Result;
                    processor.Commit();
                }
                catch
                {
                    processor.Rollback();
                }

            }
            if(!Directory.Exists(storagePath))
                throw Err.Of(this).NotFound("StorageDirectory").Ex();
            if(!SystemHelper.GetDirectoryRights(storagePath, _infrastructure.Env.WorkingIdentity)
                .HasFlag(FileSystemRights.Modify))
            {
                throw Err.Of(this).NotAuthorized("StorageDirectory").Ex();
            }
            _storagePath = storagePath;
        }

        public void Create(FileEntity file)
        {
            if(file == null)
                throw new ArgumentNullException("file");

            string guidStr = file.Guid.ToString();
            string filePath = Path.Combine(_storagePath, guidStr);
            using(var fStream = File.Create(filePath))
                fStream.SetLength(file.Size);
        }

        public bool Exists(FileEntity file)
        {
            if(file == null)
                return false;

            string guidStr = file.Guid.ToString();
            string filePath = Path.Combine(_storagePath, guidStr);
            return File.Exists(filePath);
        }

        public long Write(FileEntity file, Stream inStream)
        {
            if(file == null)
                throw new ArgumentNullException("file");
            if(inStream == null)
                throw new ArgumentNullException("inStream");

            long bytesWrittenTotal = 0;
            string guidStr = file.Guid.ToString();
            string filePath = Path.Combine(_storagePath, guidStr);
            using(var fStream = File.OpenWrite(filePath))
            {
                int bytesRead;
                var buf = new byte[BUF_SIZE];

                while((bytesRead = inStream.Read(buf, 0, buf.Length)) > 0)
                {
                    fStream.Write(buf, 0, bytesRead);
                    bytesWrittenTotal += bytesRead;
                }
            }
            return bytesWrittenTotal;
        }

        public long Read(FileEntity file, Stream outStream)
        {
            if(file == null)
                throw new ArgumentNullException("file");
            if(outStream == null)
                throw new ArgumentNullException("outStream");

            long bytesReadTotal = 0;
            string guidStr = file.Guid.ToString();
            string filePath = Path.Combine(_storagePath, guidStr);
            using(var fStream = File.OpenRead(filePath))
            {
                int bytesRead;
                var buf = new byte[BUF_SIZE];
                while((bytesRead = fStream.Read(buf, 0, buf.Length)) > 0)
                {
                    outStream.Write(buf, 0, bytesRead);
                    bytesReadTotal += bytesRead;
                }
            }
            return bytesReadTotal;
        }

        public void Delete(FileEntity file)
        {
            if(file == null)
                return;

            string guidStr = file.Guid.ToString();
            string filePath = Path.Combine(_storagePath, guidStr);
            if(!File.Exists(filePath)) 
                return;
            File.Delete(filePath);
        }

        #endregion
    }

    [Dependency("8c49cd1f-ceef-4286-a50d-3c2294eb745d",
        Lifestyle = DependencyLifestyle.Singleton,
        Name = "MongoDB File Storage")]
    public sealed class MongoFileStorage : IFileStorage, IInitDependencyOnResolve
    {
        #region Constants

        private const int BUF_SIZE = 65536;

        #endregion

        #region Fields

        private readonly IInfrastructure _infrastructure;
        private MongoDatabase _database;

        #endregion

        #region Ctors

        public MongoFileStorage(IInfrastructure infrastructure)
        {
            _infrastructure = infrastructure;
            _database = null;
        }

        #endregion

        #region Properties

        public IDictionary<String, String> State
        {
            get
            {
                var stat = _database.GetStats();
                var server = _database.Server;
                var buildInfo = server.BuildInfo;
                return new Dictionary<String, String>
                {
                    {"ServerState", server.State.ToString()},
                    {"ServerBuildBits", buildInfo.Bits.ToString()},
                    {"ServerBuildSysInfo", buildInfo.SysInfo},
                    {"ServerBuildVersion", buildInfo.VersionString},
                    {"ServerBuildGitVersion", buildInfo.GitVersion},
                    {"DatabaseName", _database.Name},
                    {"GridFSChunkSize", _database.GridFS.Settings.ChunkSize.ToString()},
                    {"StatsFileSize", stat.FileSize.ToString()},
                    {"StatsStorageSize", stat.StorageSize.ToString()},
                    {"StatsDataSize", stat.DataSize.ToString()}
                };
            }
        }

        #endregion

        #region Methods

        public void Initialize()
        {
            string connectionString = String.Empty;
            using(var ioc = _infrastructure.DependencyResolver())
            {
                var processor = ioc.Resolve<IOperationProcessor>().Init(new WorkingContext
                {
                    CID = new Guid("8c49cd1f-ceef-4286-a50d-3c2294eb745d"),
                    IsSafe = true
                });
                try
                {
                    connectionString = processor.Execute(new GetConfigParamValueByKey
                    {
                        Key = Par.In("Plugin").Of(this).Name("ConnectionString")
                    }).Result;
                    processor.Commit();
                }
                catch
                {
                    processor.Rollback();
                }
            }
            if(String.IsNullOrWhiteSpace(connectionString))
                throw Err.Of(this).NotValid("ConnectionString").Ex();
            _database = MongoDatabase.Create(connectionString);
        }

        public void Create(FileEntity file)
        {
            if(file == null)
                throw new ArgumentNullException("file");

            string guidStr = file.Guid.ToString();
            using(var fStream = _database.GridFS.Create(guidStr))
                fStream.SetLength(file.Size);
        }

        public bool Exists(FileEntity file)
        {
            if(file == null)
                return false;

            return _database.GridFS.Exists(file.Guid.ToString());
        }

        public long Write(FileEntity file, Stream inStream)
        {
            if(file == null)
                throw new ArgumentNullException("file");
            if(inStream == null)
                throw new ArgumentNullException("inStream");

            long bytesWrittenTotal = 0;
            string guidStr = file.Guid.ToString();
            using(var fStream = _database.GridFS.OpenWrite(guidStr))
            {
                int bytesRead;
                var buf = new byte[BUF_SIZE];
                while((bytesRead = inStream.Read(buf, 0, buf.Length)) > 0)
                {
                    fStream.Write(buf, 0, bytesRead);
                    bytesWrittenTotal += bytesRead;
                }
            }
            return bytesWrittenTotal;
        }

        public long Read(FileEntity file, Stream outStream)
        {
            if(file == null)
                throw new ArgumentNullException("file");
            if(outStream == null)
                throw new ArgumentNullException("outStream");

            long bytesReadTotal = 0;
            string guidStr = file.Guid.ToString();
            using(var fStream = _database.GridFS.OpenRead(guidStr))
            {
                int bytesRead;
                var buf = new byte[BUF_SIZE];
                while((bytesRead = fStream.Read(buf, 0, buf.Length)) > 0)
                {
                    outStream.Write(buf, 0, bytesRead);
                    bytesReadTotal += bytesRead;
                }
            }
            return bytesReadTotal;
        }

        public void Delete(FileEntity file)
        {
            if(file == null)
                return;

            string guidStr = file.Guid.ToString();
            if(_database.GridFS.Exists(guidStr))
                return;
            _database.GridFS.Delete(guidStr);
        }

        #endregion
    }

#if CLOUD

    [Dependency("8840bcf7-668f-4e5e-98b0-4c084fa29b62", 
        Lifestyle = DependencyLifestyle.Singleton, 
        Name = "Windows Azure Local Blob Storage")]
    public sealed class LocalBlobStorage : IFileStorage, IInitDependencyOnResolve
    {
        #region Constants

        private const int BUF_SIZE = 65536;

        #endregion

        #region Fields

        private readonly IInfrastructure _infrastructure;
        private LocalResource _storage;

        #endregion

        #region Ctors

        public LocalBlobStorage(IInfrastructure infrastructure)
        {
            _infrastructure = infrastructure;
            _storage = null;
        }

        #endregion

        #region Properties

        public IDictionary<String, String> State
        {
            get
            {
                if(_storage == null)
                    return null;

                var dirInfo = new DirectoryInfo(_storage.RootPath);
                var driveInfo = new DriveInfo(dirInfo.Root.Name);
                var fileInfos = dirInfo.GetFiles();

                return new Dictionary<String, String>
                {
                    {"Path", dirInfo.FullName},
                    {"DirectoryName", dirInfo.Name},
                    {"DirectorySize", fileInfos.Sum(x => x.Length).ToString()},
                    {"MaximumSize", (_storage.MaximumSizeInMegabytes * 1024 * 1024).ToString()},
                    {"FileCount", fileInfos.Length.ToString()},
                    {"DriveName", driveInfo.Name},
                    {"DriveLabel", driveInfo.VolumeLabel},
                    {"DriveFormat", driveInfo.DriveFormat},
                    {"DriveSize", driveInfo.TotalSize.ToString()},
                    {"DriveAvailableSpace", driveInfo.AvailableFreeSpace.ToString()},
                    {"DriveFreeSpace", driveInfo.TotalFreeSpace.ToString()}
                };
            }
        }

        #endregion

        #region Methods

        public void Initialize()
        {
            string resourceName = String.Empty;
            using(var ioc = _infrastructure.DependencyResolver())
            {
                var processor = ioc.Resolve<IOperationProcessor>().Init(new WorkingContext
                {
                    CID = new Guid("441e799f-d801-4a8c-81ea-b17fdb5a183b"),
                    IsSafe = true
                });
                try
                {
                    resourceName = processor.Execute(new GetConfigParamValueByKey
                    {
                        Key = Par.In("Plugin").Of(this).Name("ResourceName")
                    }).Result;
                    processor.Commit();
                }
                catch
                {
                    processor.Rollback();
                }
            }
            if(String.IsNullOrWhiteSpace(resourceName))
                throw Err.Of(this).NotValid("ResourceName").Ex();
            _storage = RoleEnvironment.GetLocalResource(resourceName);
        }

        public void Create(FileEntity file)
        {
            if(file == null)
                throw new ArgumentNullException("file");

            string filePath = Path.Combine(_storage.RootPath, file.Guid.ToString());

            using(var fStream = File.Create(filePath))
                fStream.SetLength(file.Size);
        }

        public bool Exists(FileEntity file)
        {
            if(file == null)
                return false;
            string filePath = Path.Combine(_storage.RootPath, file.Guid.ToString());
            return File.Exists(filePath);
        }

        public long Write(FileEntity file, Stream inStream)
        {
            if(file == null)
                throw new ArgumentNullException("file");
            if(inStream == null)
                throw new ArgumentNullException("inStream");

            long bytesWrittenTotal = 0;
            string filePath = Path.Combine(_storage.RootPath, file.Guid.ToString());

            using(var fStream = File.OpenWrite(filePath))
            {
                int bytesRead;
                var buf = new byte[BUF_SIZE];

                while((bytesRead = inStream.Read(buf, 0, buf.Length)) > 0)
                {
                    fStream.Write(buf, 0, bytesRead);
                    bytesWrittenTotal += bytesRead;
                }
            }
            return bytesWrittenTotal;
        }

        public long Read(FileEntity file, Stream outStream)
        {
            if(file == null)
                throw new ArgumentNullException("file");
            if(outStream == null)
                throw new ArgumentNullException("outStream");

            long bytesReadTotal = 0;
            string filePath = Path.Combine(_storage.RootPath, file.Guid.ToString());

            using(var fStream = File.OpenRead(filePath))
            {
                int bytesRead;
                var buf = new byte[BUF_SIZE];

                while((bytesRead = fStream.Read(buf, 0, buf.Length)) > 0)
                {
                    outStream.Write(buf, 0, bytesRead);
                    bytesReadTotal += bytesRead;
                }
            }
            return bytesReadTotal;
        }

        public void Delete(FileEntity file)
        {
            if(file == null)
                return;
            string filePath = Path.Combine(_storage.RootPath, file.Guid.ToString());

            if(!File.Exists(filePath))
                return;
            File.Delete(filePath);
        }

        #endregion
    }

#endif
}
